package site.wenneng.dev.common.util;

import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.support.IdeaProxyLambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.ReflectLambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.ShadowLambdaMeta;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import site.wenneng.dev.objs.po.WorkingHoursPO;

import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.security.InvalidParameterException;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;

import static com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda.extract;

@SuppressWarnings("unchecked")
public class MybatisUtils {

    private static final int DEFAULT_BATCH_SIZE = 1000;

    public static <E> boolean saveBatch(Class<? extends BaseMapper<E>> mapperClass, Collection<E> list) {
        return saveBatch(mapperClass, list, DEFAULT_BATCH_SIZE);
    }

    public static <E> boolean saveBatch(Class<? extends BaseMapper<E>> mapperClass, Collection<E> list, int batchSize) {
        return executeBatch(mapperClass, list, batchSize, (sqlSession, e) ->
                sqlSession.insert(SqlHelper.getSqlStatement(mapperClass, SqlMethod.INSERT_ONE), e));
    }

    public static <E> boolean executeBatch(SBiConsumer<? extends BaseMapper<E>, E> mapperMethod, Collection<E> list) {
        return executeBatch(mapperMethod, list, DEFAULT_BATCH_SIZE);
    }

    public static <E> boolean executeBatch(SBiConsumer<? extends BaseMapper<E>, E> mapperMethod, Collection<E> list, int batchSize) {
        LambdaMeta extract = extractLambdaMeta(mapperMethod);
        if (!BaseMapper.class.isAssignableFrom(extract.getInstantiatedClass())) {
            throw new IllegalArgumentException("mapperMethod参数不合法");
        }
        Class<? extends BaseMapper<E>> mapperClass = (Class<? extends BaseMapper<E>>) extract.getInstantiatedClass();
        String sqlStatement = mapperClass.getName() + StringPool.DOT + extract.getImplMethodName();
        return executeBatch(mapperClass, list, batchSize, ((sqlSession, e) -> sqlSession.update(sqlStatement, e)));
    }

    public static <E> boolean executeBatch(Class<? extends BaseMapper<E>> mapperClass, Collection<E> list, BiConsumer<SqlSession, E> consumer) {
        return executeBatch(mapperClass, list, DEFAULT_BATCH_SIZE, consumer);
    }

    public static <E> boolean executeBatch(Class<? extends BaseMapper<E>> mapperClass, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        return SqlHelper.executeBatch(getEntityClass(mapperClass), LogFactory.getLog(mapperClass), list, batchSize, consumer);
    }

    private static <E> Class<E> getEntityClass(Class<? extends BaseMapper<E>> mapperClass) {
        for (Type genericInterface : mapperClass.getGenericInterfaces()) {
            if (genericInterface instanceof ParameterizedType) {
                ParameterizedType parameterizedType = ((ParameterizedType) genericInterface);
                if (!Objects.equals(BaseMapper.class, parameterizedType.getRawType())) {
                    continue;
                }
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                if (!ArrayUtils.isEmpty(actualTypeArguments)) {
                    return (Class<E>) actualTypeArguments[0];
                }
            }
        }
        throw new InvalidParameterException("入参错误！");
    }

    public static <T, U> LambdaMeta extractLambdaMeta(SBiConsumer<T, U> func) {
        if (func instanceof Proxy) {
            return new IdeaProxyLambdaMeta((Proxy) func);
        }

        try {
            Method method = func.getClass().getDeclaredMethod("writeReplace");
            SerializedLambda serializedLambda = (SerializedLambda) ReflectionKit.setAccessible(method).invoke(func);
            return new ReflectLambdaMeta(serializedLambda, func.getClass().getClassLoader());
        } catch (Exception e) {
            return new ShadowLambdaMeta(extract(func));
        }
    }

    @FunctionalInterface
    public interface SBiConsumer<T, U> extends BiConsumer<T, U>, Serializable {
    }

}
